import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.FocusTraversalPolicy;
import java.awt.GridLayout;
import java.awt.Toolkit;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.Vector;
import java.util.concurrent.ConcurrentSkipListSet;


/**
 * TODO This class is the main dialog and main part of the program. 
 *
 * @author Mike Childers.
 *         Created Aug 9, 2012.
 */
public class AppDialog extends JDialog {
	/**
	 * serialVersionUID was added to make the compiler happy.
	 */
	private static final long serialVersionUID = 1L;
	private CellClass[][] Cells = new CellClass[10][10];
	JLabel statusLabel;
	public static class MyOwnFocusTraversalPolicy
	extends FocusTraversalPolicy
	{
	Vector<Component> order;

	public MyOwnFocusTraversalPolicy(Vector<Component> order) {
	this.order = new Vector<Component>(order.size());
	this.order.addAll(order);
	}
	public Component getComponentAfter(Container focusCycleRoot,
	                         Component aComponent)
	{
	int idx = (order.indexOf(aComponent) + 1) % order.size();
	return order.get(idx);
	}

	public Component getComponentBefore(Container focusCycleRoot,
	                          Component aComponent)
	{
	int idx = order.indexOf(aComponent) - 1;
	if (idx < 0) {
	idx = order.size() - 1;
	}
	return order.get(idx);
	}

	public Component getDefaultComponent(Container focusCycleRoot) {
	return order.get(0);
	}

	public Component getLastComponent(Container focusCycleRoot) {
	return order.lastElement();
	}

	public Component getFirstComponent(Container focusCycleRoot) {
	return order.get(0);
	}
	}

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		try {
			AppDialog dialog = new AppDialog();
			dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			dialog.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Creates the dialog.
	 */
	public AppDialog() {
		JPanel sudokuPane = new JPanel();
		int minr,maxr,minc,maxc;
		setBounds(100, 100, 450, 500);
		getContentPane().setLayout(new BorderLayout());
		{ // buttonPane Configuration.
			JPanel buttonPane = new JPanel();
			buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
			getContentPane().add(buttonPane, BorderLayout.SOUTH);
			{
				statusLabel = new JLabel();
				buttonPane.add(statusLabel);
			}
			{
				JButton solveButton = new JButton("Solve");
				solveButton.addMouseListener(new MouseAdapter() {
					@Override
					public void mouseClicked(MouseEvent e) {
						solve();
					}
				});
				solveButton.setActionCommand("OK");
				buttonPane.add(solveButton);
				getRootPane().setDefaultButton(solveButton);
			}
			{
				JButton exitButton = new JButton("Exit");
				exitButton.addMouseListener(new MouseAdapter() {
					@Override
					public void mouseClicked(MouseEvent e) {
						System.exit(0);
					}
				});
				exitButton.setActionCommand("Cancel");
				buttonPane.add(exitButton);
			}
			{
				JButton editButton = new JButton("Edit Cells");
				editButton.addMouseListener(new MouseAdapter() {
					@Override
					public void mouseClicked(MouseEvent e) {
						editCells(e.getComponent());
					}
				});
				editButton.setActionCommand("Edit");
				buttonPane.add(editButton);
			}
		} // end buttonPane Configuration.
		
		{// sudokuPane. encloses all 81 cells.
		 //	JPanel sudokuPane = new JPanel();
			sudokuPane.setLayout(new GridLayout(3,3,2,2));
			getContentPane().add(sudokuPane, BorderLayout.CENTER);
			sudokuPane.setFocusTraversalPolicyProvider(true);
			
			
			{// sudokuGroups. 9 groups which enclose 9 cells each.
				JPanel[] sudokuGroup = new JPanel[10];
				
				for(int i=1;i<=9;i++) {
					sudokuGroup[i] = new JPanel();
					sudokuGroup[i].setLayout(new GridLayout(3,3));
					sudokuPane.add(sudokuGroup[i]);
					sudokuGroup[i].setBackground(Color.white);
					sudokuGroup[i].setBorder(BorderFactory.createLineBorder(Color.black,1));
					
					

					{// sudokuCells.
						JPanel cellPane;
						switch (i) {
						case 1:	minr=1; maxr=3; minc=1; maxc=3; break;
						case 2:	minr=1; maxr=3; minc=4; maxc=6; break;
						case 3:	minr=1; maxr=3; minc=7; maxc=9; break;
						case 4:	minr=4; maxr=6; minc=1; maxc=3; break;
						case 5:	minr=4; maxr=6; minc=4; maxc=6; break;
						case 6:	minr=4; maxr=6; minc=7; maxc=9; break;
						case 7:	minr=7; maxr=9; minc=1; maxc=3; break;
						case 8:	minr=7; maxr=9; minc=4; maxc=6; break;
						case 9:	minr=7; maxr=9; minc=7; maxc=9; break;
						default: minr=maxr=minc=maxc=0;
						}
						for(int r=minr;r<=maxr;r++) {
							for(int c=minc;c<=maxc;c++) {
								cellPane = new JPanel();
								sudokuGroup[i].add(cellPane);
								Cells[r][c] = new CellClass(cellPane);
								Cells[r][c].setRowgrp(r);
								Cells[r][c].setColgrp(c);
								Cells[r][c].setBoxgrp(i);
							}
						}	
					}
				}
			}
		}
		// Create the groups. (rows, columns, and boxes)
		for(int i=0;i<=9;i++) {// The set at index 0 is never used.
			CellClass.rows[i] = new CellSet();
			CellClass.cols[i] = new CellSet();
			CellClass.boxes[i] = new CellSet();
		}
		
		for(int i=1;i<=9;i++) { 
			for(int j=1;j<=9;j++) {
				CellClass.rows[i].add(Cells[i][j]);
				CellClass.cols[j].add(Cells[i][j]);
				CellClass.boxes[Cells[i][j].getBoxgrp()].add(Cells[i][j]);
			}
		}
		
		Vector<Component> order = new Vector<Component>(7);
	    for(int i=1;i<=9;i++) {
	    	for(int j=1;j<=9;j++) {
	    		order.add(Cells[i][j].getCellPanes());
	    	}
	    }
	    MyOwnFocusTraversalPolicy newPolicy = new MyOwnFocusTraversalPolicy(order);
	    sudokuPane.setFocusTraversalPolicy(newPolicy);
	    
	    //testing purposes only.
	    int[][] samplePuzzle = { {0,0,0,0,0,0,0,0,0,0},
	    						 {0,0,0,0,7,0,0,1,0,0},
	    						 {0,0,7,9,0,1,3,0,0,0},
	    						 {0,5,0,0,0,0,0,9,0,0},
	    						 {0,1,6,0,0,5,0,7,3,8},
	    						 {0,0,8,0,6,0,2,0,1,0},
	    						 {0,9,4,7,0,8,0,0,6,5},
	    						 {0,0,0,3,0,0,0,0,0,6},
	    						 {0,0,0,0,8,3,0,5,9,0},
	    						 {0,0,0,4,0,0,6,0,0,0} };
	    
	    for(int i=1;i<=9;i++) {
			for(int j=1;j<=9;j++) {
				if(samplePuzzle[i][j] != 0) {
					Cells[i][j].initialEntry(samplePuzzle[i][j]);
				}
			}
		}
	    
		this.statusLabel.setText("Cells Loaded.");
	} // End of AppDialog() Constructor
	
	public void editCells(java.awt.Component button) {
		String editString = "Edit Cells";
		String doneString = "Quit Edit";
		if(((JButton) button).getText().equals(editString)) {
			((JButton) button).setText(doneString);
			
			for(int i=1;i<=9;i++) {
				for(int j=1;j<=9;j++) {
					Cells[i][j].resetPossibles();
					Cells[i][j].setEditable(true);
				}
			}
			Cells[1][1].requestFocus();
			
		} else {
			for(int i=1;i<=9;i++) {// validate the cells for duplicates.
				for(int j=1;j<=9;j++) {
					if(CellClass.rows[Cells[i][j].getRowgrp()].containsDup(Cells[i][j]) ||
							CellClass.cols[Cells[i][j].getColgrp()].containsDup(Cells[i][j]) ||
							CellClass.boxes[Cells[i][j].getBoxgrp()].containsDup(Cells[i][j])
							) {
						// error! Duplicates!
						this.statusLabel.setForeground(Color.red);
						this.statusLabel.setText("ERROR! Conflicting Cells! ");
						Toolkit.getDefaultToolkit().beep(); // Beep for the error.
						return;
					} else {
						this.statusLabel.setForeground(Color.black);
						this.statusLabel.setText("");
						
					}
				}
			}
			for(int i=1;i<=9;i++) {
				for(int j=1;j<=9;j++) {
					Cells[i][j].setFieldToData();
					Cells[i][j].setEditable(false);
					((JButton) button).setText(editString);
				}
			}
		}
	} //End of editCells()

	private void solve() {
		boolean result;
		do {// if one rule is true (i.e. solves at least one cell), the process must start over from the first rule.
			result = nakedSingleSolver();
			if(result == true) continue;
			
			result = nakedSubsetSolver();
			if(result == true) continue;
			
		} while (result == true);
			
	}
	/**
	 *  nakedSingleSolver searches the grid for cells that have only one possible value and solves them.
	 *  @return boolean true or false indicating whether it solved any cells or not.
	 */	
	private boolean nakedSingleSolver() {
		boolean result = false;
		for(int i=1;i<=9;i++) {
			for(int j=1;j<=9;j++) {
				if(Cells[i][j].Solve() == true) {
					result = true;
				}
			}
		}
		return result;
	}
	
	private boolean nakedSubsetSolver() {
		// This function finds subsets within each set that isolate what must be contained within them.
		// two sets with the same two numbers (or less) in each cell and no other numbers, indicates
		boolean result = false;
		CellSet[][] sets = {CellClass.rows, CellClass.cols, CellClass.boxes};
		ConcurrentSkipListSet<Integer> workingSet = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSeta = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSetb = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSetc = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSetd = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSete = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSetf = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSetg = new ConcurrentSkipListSet<Integer>();
		ConcurrentSkipListSet<Integer> subSeth = new ConcurrentSkipListSet<Integer>();
		CellSet group = new CellSet();
		for(CellSet[] set: sets) {
			for(CellSet grp: set) { //check each set of cells in rows, columns, and boxes, each as group.
				if(grp.isEmpty()) continue;
				group.clear();
				for(CellClass cell: grp) {//remove all of the solved cells from the group.
					if(!cell.isEmpty()) group.add(cell);
				}
				
				for(CellClass cell: group) 
					workingSet.addAll(cell.getSet());
				if(workingSet.size() > 2) {//two numbers and two spaces are a naked set, but cannot be reduced because 
														// they aren't a subset of a bigger set. if size() is 3 or larger,
														// a subset of two is useful.
														// There must be three open cells with a subset of two cells with 
														// two possibilities in order to eliminate the third cell.
				
					for(int subSetSize=2;subSetSize<=workingSet.size()-1;subSetSize++) {// setSize cells with setSize possibles, etc.
						for(int a: workingSet) {
							subSeta.clear();
							subSeta.add(a);
							for(int b: workingSet) {
								if(a < b) {
									subSetb.clear();
									subSetb.addAll(subSeta);
									subSetb.add(b);
								} else continue;
								if(subSetSize == 2) {
									result = nakedSubsetRoutine(subSetb, group);
									if(result == true) return result; //The rule should quit after the first successful
									continue;						  //elimination and restart with the first rule.
								} else
									for(int c: workingSet) {
										if(b < c) {
											subSetc.clear();
											subSetc.addAll(subSetb);
											subSetc.add(c);
										} else continue;
										if(subSetSize == 3) {
											result = nakedSubsetRoutine(subSetc, group);
											if(result == true) return result;
										} else 
											for(int d: workingSet) {
												if(c < d) {
													subSetd.clear();
													subSetd.addAll(subSetc);
													subSetd.add(d);
												} else continue;
												if(subSetSize == 4) {
													result = nakedSubsetRoutine(subSetd, group);
													if(result == true) return result;
													continue;
												} else
													for(int e: workingSet) {
														if(d < e) {
															subSete.clear();
															subSete.addAll(subSetd);
															subSete.add(e);
														} else continue;
														if(subSetSize == 5) {
															result = nakedSubsetRoutine(subSete, group);
															if(result == true) return result;
															continue;
														} else
															for(int f: workingSet) {
																if(e < f) {
																	subSetf.clear();
																	subSetf.addAll(subSete);
																	subSetf.add(f);
																} else continue;
																if(subSetSize == 6) {
																	result = nakedSubsetRoutine(subSetf, group);
																	if(result == true) return result;
																	continue;
																} else
																	for(int g: workingSet) {
																		if(f < g) {
																			subSetg.clear();
																			subSetg.addAll(subSetf);
																			subSetg.add(g);
																		} else continue;
																		if(subSetSize == 7) {
																			result = nakedSubsetRoutine(subSetg, group);
																			if(result == true) return result;
																			continue;
																		} else
																			for(int h: workingSet) {
																				if(g < h) {
																					subSeth.clear();
																					subSeth.addAll(subSetg);
																					subSeth.add(h);
																				} else continue;
																				result = nakedSubsetRoutine(subSeth, group);
																				if(result == true) return result;
																			}
																	}
															}
													}
											}
									}
							}
						}
					}
				}
			}
		}
		return result;
		
	}
	
	private boolean nakedSubsetRoutine(ConcurrentSkipListSet<Integer> subSet, CellSet group) {
		boolean result = false;
		CellSet cellList = new CellSet();
		CellSet tempList = new CellSet();
		ConcurrentSkipListSet<Integer> tempSet = new ConcurrentSkipListSet<Integer>();
		
		for(CellClass cell: group) {
			tempSet.clear();
			tempSet.addAll(cell.getSet());
			tempSet.removeAll(subSet);
			if(tempSet.isEmpty()) {
				cellList.add(cell);
			}
		}
		if(cellList.size() == subSet.size()) {
			result = true;
			tempList.clear();
			tempList.addAll(group);
			tempList.removeAll(cellList);
			for(CellClass cell: tempList) {
				cell.removeAll(subSet);
			}
		}
		
		if(result == true) 
			System.out.println(subSet.toString()+"="+group.toString());
		
		return result;
	}// End of nakedSubsetRoutine()
}// End of AppDialog class


